package com.kamistoat.netty.nioprac;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * 创建一个NIO服务器，以非阻塞的方式监听客户端链接
 */
public class NIOServer {
    public static void main(String[] args) throws Exception {
        // 创建ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        // 创建一个Selector对象
        Selector selector = Selector.open();

        // 为ServerSocketChannel绑定一个端口. ServeSocketChannel是用于监听客户端链接，并为连接分配SocketChannel的
        // ServerSocketChannel.socket() 获取一个 ServerSocket，然后为其绑定监听端口
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        // 设置为非阻塞，直接在 ServerSocketChannel.ConfigureBlocking()中设置.
        // 只有非阻塞的 SocketChannel才能和Selector进行绑定
        serverSocketChannel.configureBlocking(false);

        // 将ServerSocketChannel 注册到 Selector上，且该channel关心的事件是 OP_ACCEPT事件
        // 因为这个顶层的 Channel其作用仅仅是为新的连接分配新Channel，在它上面发生的事件只可能是 OP_ACCEPT
        // 并且对于 Selector而言，当监听到 OP_ACCEPT事件时，一定是发生在这个 ServerSocketChannel 上。
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            // 调用Selector的select()，开始轮询所有注册的channel(在最开始时，只有一个ServerSocketChannel)
            // 阻塞等待1秒，如果无事件，则执行到if内部
            if (selector.select(1000) == 0) {
                System.out.println("服务器等待1秒，无连接，continue");
                continue;
            }

            // 如果没有经过上面的continue，则说明至少有一个channel具备事件
            // 注意，当Selector发现有channel事件时，会自动将其SelectionKey保存下来.
            // 此时调用 Selector.selectedKeys()获得的就是上一个select()期间发现的selectionKey
            Set<SelectionKey> selectionKeys = selector.selectedKeys();

            // 再通过SelectionKey集合反向获取channel
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
            while (keyIterator.hasNext()) {
                // 获取到SelectionKey
                SelectionKey key = keyIterator.next();

                // 根据key，对相应的通道做对应处理
                if (key.isAcceptable()) {
                    // 如果发生的事件是 OP_ACCEPT，则一定是在 ServerSocketChannel上发生的，所以不用再用 key.channel()了，直接对 SeverSocketChannel唯一处理
                    // 为新的客户端连接分配新的SocketChannel
                    // 注意，BIO中使用 accept()是阻塞的，是因为那里的accept()并不知道是否真的有新连接
                    // 而在NIO中，由于上面的 isAcceptable() 确保了一定是有新连接，所以这里的accept一定能成功执行
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    // 将该 SocketChannel设置为非阻塞的.
                    socketChannel.configureBlocking(false);
                    // 将为客户端连接分配的新Channel注册到Selector中，并关注该channel的读就绪事件，并为这个Channel绑定一个buffer
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                    System.out.println("客户端连接成功:" + socketChannel.hashCode());
                } else if (key.isReadable()) {
                    // 如果发生的事件是 OP_READ，则无法确定到底是哪个channel上发生的，则需要反向获取channel进行处理
                    // 通过Key反向获取对应channel
                    SocketChannel channel = (SocketChannel) key.channel();
                    // 获取到channel对应的buffer
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    // 读就绪事件，则读取channel中的数据到buffer中
                    channel.read(buffer);
                    System.out.println("form 客户端" + new String(buffer.array()));
                }

                // 手动从集合中移除当前的SelectionKey，防止重复操作
                keyIterator.remove();
            }
        }
    }
}
